﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NUnit.Framework;

using Moq;

using SRTSolutions.Elevate;

namespace SRTSolutions.Elevate.Guide._001_Sequences._001_CreatingSequences
{
#if NET_3_5
    using MyTuple=Tuple;
#else
    using MyTuple=System.Tuple;
#endif
    public class _003_AdvancedSequenceGeneration
    {

        [Test]
        public void CreateFromDisposableReturnsTheValuesOfTheGenerator()
        {
            //given a function that creates an IDisposable
            Func<MemoryStream> getStream = GetStream;

            //and a function that creates a sequence from
            //the IDisposable.
            Func<MemoryStream, IEnumerable<string>> getLines = GetLines;

            //we can use CreateFromDisposable to pass the disposable
            //to the sequence generator like this:
            var sequence = Seq.CreateFromDisposable(getStream, getLines);

            var expected = Seq.Build("Alpha", "Beta", "Gamma");
            Assert.AreEqual(expected, sequence);
        }

        public MemoryStream GetStream()
        {
            var stream = new MemoryStream();

            var writer = new StreamWriter(stream);
            Seq.Build("Alpha", "Beta", "Gamma")
                .ForEach(value => writer.WriteLine(value));

            writer.Flush();
            stream.Position = 0;

            return stream;
        }

        public IEnumerable<string> GetLines(Stream stream)
        {
            var reader = new StreamReader(stream);

            while (!reader.EndOfStream)
            {
                yield return reader.ReadLine();
            }
        }

        [Test]
        public void CreateFromDisposableDisposesOfTheDisposable()
        {
            //given a function that creates an IDisposable
            var mock = new Mock<TextReader>();
            mock.As<IDisposable>();
            var calledCount = 0;
            Func<TextReader> getReader = () => { calledCount++; return mock.Object; };

            //and a function that creates a sequence from the IDisposable
            Func<TextReader, IEnumerable<string>> getLines = GetLines;

            //we can create a sequence as above using CreateFromDisposable
            var sequence = Seq.CreateFromDisposable(getReader, getLines);

            //initially, the IDisposable creator hasn't been called
            Assert.AreEqual(0, calledCount);

            //but when we enumerate the sequence, the creator gets called,
            //and the IDisposable is disposed
            foreach (var value in sequence) { }
            Assert.AreEqual(1, calledCount);
            mock.As<IDisposable>().Verify(disposable => disposable.Dispose(), Moq.Times.Once());

            //this happens each time we enumerate
            foreach (var value in sequence) { }
            Assert.AreEqual(2, calledCount);
            mock.As<IDisposable>().Verify(disposable => disposable.Dispose(), Moq.Times.Exactly(2));
        }

        public IEnumerable<string> GetLines(TextReader reader)
        {
            var data =reader.ReadLine();
            while (data != null)
            {
                yield return data;
                data = reader.ReadLine();
            }
        }


        [Test]
        public void Delay()
        {
            //given a function that generates a sequence
            int timesExecuted = 0;
            Func<IEnumerable<int>> generatorFunction =
                () =>
                {
                    timesExecuted++;
                    return 1.Through(10);
                };

            //using Delay, we can get an enumerable with the same values
            //as the enumerable returned by the source function but only 
            //execute the function when an enumerator is requested
            var delayed = Seq.Delay(generatorFunction);

            //see, the generator function has not been called yet!
            Assert.AreEqual(0, timesExecuted);

            //but when we walk the sequence, the output sequence is the result
            //returned by the generator function, and we can see that the 
            //generator function gets applied.
            Assert.AreEqual(1.Through(10), delayed);
            Assert.AreEqual(1, timesExecuted);
        }

        [Test]
        public void DelayExecutesTheGeneratorEachTimeTheSequenceIsEnumerated()
        {
            //given a delayed sequence
            int timesExecuted = 0;
            Func<IEnumerable<int>> generatorFunction =
                () =>
                {
                    timesExecuted++;
                    return 1.Through(10);
                };

            var delayed = Seq.Delay(generatorFunction);

            //if we enumerate the sequence 3 times.
            delayed.ForEach(x => {var tmp = x;});
            delayed.ForEach(x => {var tmp = x;});
            delayed.ForEach(x => {var tmp = x;});

            //the generating function will be executed 3 times
            Assert.AreEqual(3, timesExecuted);
        } 

        [Test]
        public void Times()
        {
            //if we have a function that generates a value based on a number
            Func<int, int> square = value => value * value;

            //we can execute it a number of times to create a sequence like this
            var tenSquares = 10.Times(square);

            Assert.AreEqual(Seq.Build(0, 1, 4, 9, 16, 25, 36, 49, 64, 81), tenSquares);
        }

        [Test]
        public void TimesWithZero()
        {
            //if we call times on 0
            var result = 0.Times(x => x * x);

            //we will get an empty sequence
            Assert.AreEqual(Seq.Build<int>(), result);
        }

        [Test]
        public void TimesWithParameterlessFunc()
        {
            //we can also use times with a parameterless func
            var fiveFives = 5.Times(() => 5);

            Assert.AreEqual(Seq.Build(5, 5, 5, 5, 5), fiveFives);
        }

        [Test]
        public void GeneratingInfiniteSequences()
        {
            //we saw above how we can execute a function N number of times
            //to generate a sequence. If we want to do the same thing
            //out to an infinite number, we can do that like this
            var allSquares = Seq.InfiniteTimes(x => x * x);

            Assert.AreEqual(100.Times(x => x * x), allSquares.Take(100));

            //NOTE: Technically, the sequence isn't infinite because of limitations on
            //      the max integer value, but for most purposes, it's close enough, right?
        }

        /// <summary>
        /// Unfolding is a sequence generation technique that lets us build an arbitrarily
        /// long sequence using a generator function. It allows you to pass a state 
        /// variable from one application of the generator function to the next.
        /// </summary>
        [Test]
        public void Unfold()
        {
            //given a function that generates the next number
            //in the Fibonacci sequence based on two input values
#if NET_3_5
            Func<int, int, Option<Tuple<int, Tuple<int, int>>>> 
#else
            Func<int, int, Option<System.Tuple<int, System.Tuple<int, int>>>> 
#endif
            getNext =
                (current, next) =>
                    UnfoldHelpers.CreateResult(current, MyTuple.Create(next, next + current));

            //we can use Unfold to generate the Fibonacci numbers.
            var fibs =
                Seq.Unfold(MyTuple.Create(0, 1), state => state.Select(getNext));

            var results = Seq.Build(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89);
            Assert.AreEqual(results, fibs.Take(results.Count()));
        }

        [Test]
        public void UnfoldSameInputAndOutputType()
        {
            //we can use unfold without having to use a tuple like this
            //here we're generating all the even numbers less than 1000
            var evens = Seq.Unfold(2, x => x < 998 ? Option.Some(x + 2) : Option<int>.None);

            var results = 1.Through(499).Select(x => x * 2);

            Assert.AreEqual(results, evens);
        }

        #region Unit Tests

        [Test]
        public void CreateFromDisposableCreatorNull()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.CreateFromDisposable((Func<Stream>)null, tmp => Seq.Build<string>()));
        }

        [Test]
        public void CreateFromDisposableGeneratorNull()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.CreateFromDisposable(()=> new MemoryStream(), (Func<MemoryStream, IEnumerable<int>>)null));
        }

        [Test]
        public void DelayArgumentNullException()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.Delay<int>(null));
        }

        [Test]
        public void TimesThisException()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => (-1).Times(x => x));
        }

        [Test]
        public void TimesArgumentException()
        {
            Assert.Throws<ArgumentNullException>(() => 10.Times((Func<int, int>)null));
        }

        [Test]
        public void TimesParameterlessThisException()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => (-1).Times(() => 1));
        }

        [Test]
        public void TimesParameterlessArgumentException()
        {
            Assert.Throws<ArgumentOutOfRangeException>(() => (-1).Times((Func<int>)null));
        }

        [Test]
        public void InfiniteTimesArgumentException()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.InfiniteTimes<int>(null));
        }

        [Test]
        public void UnfoldFirstArgumentException()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.Unfold<string, string>(null, state => UnfoldHelpers.CreateResult(state, state)));
        }

        [Test]
        public void UnfoldSecondArgumentException()
        {
#if NET_3_5
            Func<string, Option<Tuple<string, string>>> tmp = null;
#else
            Func<string, Option<System.Tuple<string,string>>> tmp =null;
#endif
            Assert.Throws<ArgumentNullException>(() => Seq.Unfold<string, string>("", tmp));
        }

        [Test]
        public void UnfoldSameInputAndOutputFirstArgumentException()
        {
            Assert.Throws<ArgumentNullException>(() => Seq.Unfold<string>(null, stringValue => Option<string>.None));
        }

        [Test]
        public void UnfoldSameInputAndOutputSecondArgumentException()
        {
            Func<string, Option<string>> tmp = null;
            Assert.Throws<ArgumentNullException>(() => Seq.Unfold<string>("", tmp));
        }

        #endregion

    }
}
